package com.mechanatrix.auto;

import com.jme3.app.SimpleApplication;
import com.jme3.bullet.BulletAppState;
import com.jme3.bullet.PhysicsSpace;
import com.jme3.bullet.collision.shapes.CompoundCollisionShape;
import com.jme3.bullet.collision.shapes.CylinderCollisionShape;
import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.bullet.control.VehicleControl;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.AnalogListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.light.AmbientLight;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Matrix3f;
import com.jme3.math.Vector2f;
import com.jme3.math.Vector3f;
import com.jme3.renderer.queue.RenderQueue;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.shape.Box;
import com.jme3.shadow.DirectionalLightShadowRenderer;
import com.jme3.util.SkyFactory;
import com.mechanatrix.input.ThirdPersonCamera;
import java.util.concurrent.atomic.AtomicReference;

public class TestAuto extends SimpleApplication implements AnalogListener, ActionListener {

	private final static String JoystickLeft = "JoystickLeft";
	private final static String JoystickRight = "JoystickRight";
	private final static String JoystickDown = "JoystickDown";
	private final static String JoystickUp = "JoystickUp";
	private Node vehicleNode;
	private ThirdPersonCamera cameraControl;
	private BulletAppState bulletAppState;
	private VehicleControl vehicle;
	private final float accelerationForce = 500.0f;
	private final float brakeForce = 50.0f;
	private float steeringValue = 0;
	private float accelerationValue = 0;

	private enum ENGINE {

		RUNNING,
		OFF
	};

	private enum GEAR {

		FORWARD,
		REVERSE
	};
	private AtomicReference<ENGINE> engine = new AtomicReference(ENGINE.RUNNING);
	private AtomicReference<GEAR> gear = new AtomicReference(GEAR.FORWARD);

	public static void main(String[] args) {
		TestAuto app = new TestAuto();
		app.start();
	}

	@Override
	public void simpleInitApp() {
		System.out.printf("%nM E C H A N A T R I X%n---------------------%nLtd.  Liability   Co.%n%n");
		initPhysics();
		initTerrain();
		initLighting();
		initPlayer();
		initCamera();
		initAudio();
		initInput();
	}

	private PhysicsSpace getPhysicsSpace() {
		return bulletAppState.getPhysicsSpace();
	}

	private void initPhysics() {
		bulletAppState = new BulletAppState();
		stateManager.attach(bulletAppState);
		//bulletAppState.setDebugEnabled(true);
	}

	private void initTerrain() {
		PhysicsSpace space = getPhysicsSpace();
		//Material granite_01 = assetManager.loadMaterial("MatDefs/granite_01.j3m");
		Material grass_02 = assetManager.loadMaterial("MatDefs/grass_02.j3m");
		//Material metal_plates_18 = assetManager.loadMaterial("MatDefs/metal_plates_18.j3m");
		//TODO Fix ground texture.
		//Material ground_01 = assetManager.loadMaterial("MatDefs/ground_01.j3m");
		//Material metal_plates_06 = assetManager.loadMaterial("MatDefs/metal_plates_06.j3m");
		//Material material = new Material(assetManager, "Common/MatDefs/Misc/ShowNormals.j3md");

		Box floorBox = new Box(512, 0.25f, 512);
		//Box floorBox = new Box(64, 0.25f, 64);
		floorBox.scaleTextureCoordinates(new Vector2f(256, 256));
		Geometry floorGeometry = new Geometry("Floor", floorBox);
		floorGeometry.setShadowMode(RenderQueue.ShadowMode.Receive);
		floorGeometry.setMaterial(grass_02);
		//floorGeometry.setMaterial(material);
		//floorGeometry.setMaterial(grass_02);
		floorGeometry.setLocalTranslation(0, -0.0125f, 0);
		floorGeometry.addControl(new RigidBodyControl(0));
		rootNode.attachChild(floorGeometry);
		space.add(floorGeometry);


		Spatial station = assetManager.loadModel("Models/architecture/station.j3o");
		station.setQueueBucket(RenderQueue.Bucket.Transparent);
		//station.rotate(0f, -FastMath.HALF_PI, 0f);
		station.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
		station.setLocalTranslation(10f, 0.25f, 0f); //TODO Fix y!
		station.scale(0.0011f);
		rootNode.attachChild(station);

		station.addControl(new RigidBodyControl(0));
		bulletAppState.getPhysicsSpace().addAll(station);





		Spatial yucca = assetManager.loadModel("Models/flora/yucca.j3o");
		yucca.scale(0.01f);
		yucca.setLocalTranslation(-10f, 0.25f, -10.0f); //left & down & front
		yucca.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
		rootNode.attachChild(yucca);

		Spatial saguaro = assetManager.loadModel("Models/flora/saguaro.j3o");
		saguaro.scale(0.002f);
		saguaro.setLocalTranslation(10f, 0.25f, 10.0f);
		saguaro.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
		rootNode.attachChild(saguaro);

		Spatial pennisetum = assetManager.loadModel("Models/flora/pennisetum.j3o");
		pennisetum.scale(0.002f);
		pennisetum.setLocalTranslation(12f, 0.25f, 12f);
		pennisetum.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
		rootNode.attachChild(pennisetum);
		
		Spatial mytrillocactus = assetManager.loadModel("Models/flora/mytrillocactus.j3o");
		mytrillocactus.scale(0.002f);
		mytrillocactus.setLocalTranslation(0f, 0.25f, 0f);
		mytrillocactus.setShadowMode(RenderQueue.ShadowMode.CastAndReceive);
		rootNode.attachChild(mytrillocactus);

		/*
		 Spatial crystals = assetManager.loadModel("Models/crystals/crystals.obj");
		 crystals.setLocalTranslation(15f, -0.5f, -5.0f); // down & front
		 //crystals.setLocalScale(0.05f); // 1/20th
		 //cod.rotate(0.0f, (float)Math.PI / -2f, 0.0f); // clockwise 90 degrees
		 rootNode.attachChild(crystals);
		 */
		/*
		 Spatial yucca = assetManager.loadModel("Models/yucca/yucca.obj");
		 yucca.scale(0.025f);
		 yucca.setLocalTranslation(-10f, 0f, -10.0f); //left & down & front
		 //yucca.setMaterial(material);
		 rootNode.attachChild(yucca);
		 */
		/*
		 Spatial watchtower  = assetManager.loadModel("Models/watchtower/watchtower.obj");
		 watchtower.scale(0.025f);
		 watchtower.setLocalTranslation(10f, 0f, 20.0f);
		 rootNode.attachChild(watchtower);

		 */
		//Material material = new Material(assetManager, "Common/MatDefs/Misc/ShowNormals.j3md");
		//loco.model.setMaterial(material);
		/*
		 Spatial gas = assetManager.loadModel("Models/gas/gas storage.obj");
		 //pennisetum.scale(0.00375f);
		 gas.scale(0.001f);
		 //gas.scale(0.01f);
		 gas.setLocalTranslation(12f, 0f, 12f);
		 rootNode.attachChild(gas);
		 */
	}

	private void initPlayer() {
		Material mat = new Material(getAssetManager(), "Common/MatDefs/Misc/Unshaded.j3md");
		mat.getAdditionalRenderState().setWireframe(true);
		mat.setColor("Color", ColorRGBA.Red);

		//create a compound shape and attach the BoxCollisionShape for the car body at 0,1,0
		//this shifts the effective center of mass of the BoxCollisionShape to 0,-1,0
		CompoundCollisionShape compoundShape = new CompoundCollisionShape();
		//BoxCollisionShape box = new BoxCollisionShape(new Vector3f(1.2f, 0.5f, 2.4f));
		//compoundShape.addChildShape(box, new Vector3f(0, 1, 0));
		CylinderCollisionShape cylinder = new CylinderCollisionShape(new Vector3f(0.6f, 0.5f, 2.4f));
		compoundShape.addChildShape(cylinder, new Vector3f(0, 1, 0));

		//create vehicle node
		vehicleNode = new Node("vehicleNode");
		vehicle = new VehicleControl(compoundShape, 800);
		vehicleNode.addControl(vehicle);

		Spatial model = assetManager.loadModel("Models/auto/Oldtimer.j3o");
		AutoParser parser = new AutoParser();
		model.breadthFirstTraversal(parser);
		vehicleNode.attachChild(model);

		//suspension
		float stiffness = 15.0f;
		float compValue = .3f; //(should be lower than damp)
		float dampValue = .4f;
		vehicle.setSuspensionCompression(compValue * 2.0f * FastMath.sqrt(stiffness));
		vehicle.setSuspensionDamping(dampValue * 2.0f * FastMath.sqrt(stiffness));
		vehicle.setSuspensionStiffness(stiffness);
		vehicle.setMaxSuspensionForce(10000.0f);

		//wheels
		Vector3f wheelDirection = new Vector3f(0, -1, 0);
		Vector3f wheelAxle = new Vector3f(-1, 0, 0);
		float wheelRadius = 0.5f;
		float restLength = 0.4f;
		float axleHeight = 0.8f;
		float axleWidth = 1f;
		float axleSpacing = 2f;

		Spatial westWheel = assetManager.loadModel("Models/wheels/LeftTire.j3o");
		Spatial eastWheel = assetManager.loadModel("Models/wheels/RightTire.j3o");

		Spatial node1 = westWheel.clone(true);
		vehicle.addWheel(node1, new Vector3f(-axleWidth, axleHeight, axleSpacing),
			wheelDirection, wheelAxle, restLength, wheelRadius, true);

		Spatial node2 = eastWheel.clone(true);
		vehicle.addWheel(node2, new Vector3f(axleWidth, axleHeight, axleSpacing),
			wheelDirection, wheelAxle, restLength, wheelRadius, true);

		Spatial node3 = westWheel.clone(true);
		vehicle.addWheel(node3, new Vector3f(-axleWidth, axleHeight, -axleSpacing),
			wheelDirection, wheelAxle, restLength, wheelRadius, false);

		Spatial node4 = eastWheel.clone(true);
		vehicle.addWheel(node4, new Vector3f(axleWidth, axleHeight, -axleSpacing),
			wheelDirection, wheelAxle, restLength, wheelRadius, false);

		vehicleNode.attachChild(node1);
		vehicleNode.attachChild(node2);
		vehicleNode.attachChild(node3);
		vehicleNode.attachChild(node4);
		rootNode.attachChild(vehicleNode);

		getPhysicsSpace().add(vehicle);
	}

	private void initCamera() {
		flyCam.setEnabled(false);
		cameraControl = new ThirdPersonCamera(cam, vehicleNode, inputManager);
		cameraControl.setSmoothMotion(true);
		vehicleNode.addControl(cameraControl);
		/*
		 //TODO Adjust these valuse with the scale of the player model.
		 //cameraControl.setChasingSensitivity(0.0001f);
		 cameraControl.setTrailingRotationInertia(0.03f);
		 cameraControl.setMinDistance(20f);
		 cameraControl.setDefaultDistance(40);
		 cameraControl.setMaxDistance(60f);
		 cameraControl.setMinVerticalRotation(0.15f);
		 */
	}

	private void initLighting() {
		DirectionalLight sun = new DirectionalLight();
		sun.setDirection(new Vector3f(-0.1f, -0.7f, 1.0f));
		rootNode.addLight(sun);

		AmbientLight light = new AmbientLight();
		light.setColor(ColorRGBA.Gray);
		rootNode.addLight(light);

		DirectionalLightShadowRenderer dlsr = new DirectionalLightShadowRenderer(assetManager, 1024, 3);
		dlsr.setLight(sun);
		//dlsr.setLambda(0.55f);
		//dlsr.setShadowIntensity(0.6f);
		//dlsr.setEdgeFilteringMode(EdgeFilteringMode.PCF8);
		viewPort.addProcessor(dlsr);

		//viewPort.setBackgroundColor(ColorRGBA.LightGray.mult(ColorRGBA.Pink));
		//rootNode.attachChild(SkyFactory.createSky(assetManager, "Textures/Hot Summer Day.dds", false));
		rootNode.attachChild(SkyFactory.createSky(assetManager, "Textures/Cool Cloud Sunset.dds", false));
		//rootNode.attachChild(SkyFactory.createSky(assetManager, "Textures/Bright Starry Night.dds", false));
		//rootNode.attachChild(SkyFactory.createSky(assetManager, "Textures/Peacefully Morning.dds", false));
	}

	private void initAudio() {
	}

	private void initInput() {
		inputManager.addMapping("Ups", new KeyTrigger(KeyInput.KEY_W));
		inputManager.addMapping("Lefts", new KeyTrigger(KeyInput.KEY_A));
		inputManager.addMapping("Downs", new KeyTrigger(KeyInput.KEY_S));
		inputManager.addMapping("Rights", new KeyTrigger(KeyInput.KEY_D));

		inputManager.addMapping("Space", new KeyTrigger(KeyInput.KEY_SPACE));
		inputManager.addMapping("Reset", new KeyTrigger(KeyInput.KEY_RETURN));

		inputManager.addMapping("Forward", new KeyTrigger(KeyInput.KEY_F));
		inputManager.addMapping("Reverse", new KeyTrigger(KeyInput.KEY_R));

		inputManager.addListener(this, "Ups");
		inputManager.addListener(this, "Lefts");
		inputManager.addListener(this, "Downs");
		inputManager.addListener(this, "Rights");

		inputManager.addListener(this, "Space");
		inputManager.addListener(this, "Reset");

		inputManager.addListener(this, "Forward");
		inputManager.addListener(this, "Reverse");
	}

	@Override
	public void simpleUpdate(float tpf) {
		//cam.lookAt(vehicle.getPhysicsLocation(), Vector3f.UNIT_Y);
	}

	@Override
	public void onAction(String name, boolean isPressed, float tpf) {
		if (isPressed) {
			switch (name) {
				case "Lefts":
					steeringValue += .25f;
					vehicle.steer(steeringValue);
					break;
				case "Rights":
					steeringValue += -.25f;
					vehicle.steer(steeringValue);
					break;
				case "Ups":
					if (gear.get().equals(GEAR.FORWARD)) {
						accelerationValue += accelerationForce;
					} else if (gear.get().equals(GEAR.REVERSE)) {
						accelerationValue -= accelerationForce;
					}
					vehicle.accelerate(accelerationValue);
					break;
				case "Downs":
					vehicle.brake(brakeForce);
					break;
				case "Space":
					if (engine.compareAndSet(ENGINE.OFF, ENGINE.RUNNING)) {
						vehicleNode.addControl(vehicle);
					} else if (engine.compareAndSet(ENGINE.RUNNING, ENGINE.OFF)) {
						vehicle.setLinearVelocity(Vector3f.ZERO);
						vehicle.setAngularVelocity(Vector3f.ZERO);
						vehicle.resetSuspension();
						vehicleNode.removeControl(vehicle);
					}
					break;
				case "Reset":
					System.out.println("Reset");
					vehicle.setPhysicsLocation(Vector3f.ZERO);
					vehicle.setPhysicsRotation(new Matrix3f());
					vehicle.setLinearVelocity(Vector3f.ZERO);
					vehicle.setAngularVelocity(Vector3f.ZERO);
					vehicle.resetSuspension();
					break;
				case "Forward":
					gear.compareAndSet(GEAR.REVERSE, GEAR.FORWARD);
					System.out.printf("Gear = %s%n", gear.get().name());
					break;
				case "Reverse":
					gear.compareAndSet(GEAR.FORWARD, GEAR.REVERSE);
					System.out.printf("Gear = %s%n", gear.get().name());
					break;
			}
		} else {
			switch (name) {
				case "Lefts":
					steeringValue += -.25f;
					vehicle.steer(steeringValue);
					break;
				case "Rights":
					steeringValue += .25f;
					vehicle.steer(steeringValue);
					break;
				case "Ups":
					if (gear.get().equals(GEAR.FORWARD)) {
						accelerationValue -= accelerationForce;
					} else if (gear.get().equals(GEAR.REVERSE)) {
						accelerationValue += accelerationForce;
					}
					vehicle.accelerate(accelerationValue);
					break;
				case "Downs":
					vehicle.brake(0f);
					break;
			}
		}
	}

	@Override
	public void onAnalog(String name, float value, float tpf) {
		//x = 0, y = 1, z = 2, rz = 3
		switch (name) {
			case JoystickLeft:
				//horizontalRotate(-value);
				break;
			case JoystickRight:
				//horizontalRotate(value);
				break;
			case JoystickUp:
				//verticalRotate(value);
				break;
			case JoystickDown:
				//verticalRotate(-value);
				break;
		}
	}

	private void cloneAndPlace(Spatial spatial, float x, float y, float z) {
		Spatial clone = spatial.clone();
		clone.move(x, y, z);
		rootNode.attachChild(clone);
	}
}
